/**
 * @author     Gabor Greif <ggreif@gmail.com>
 * @copyright  2009-2011 Gabor Greif
 * @license    http://www.opensource.org/licenses/mit-license.php  MIT License
 * @version    Release: 0.0
 */

#include "CommandMap.hpp"

#include <iostream>

#include "cError.hpp"

/// Some out-of line definitions
///
namespace micker {
    std::string Command::quote(const std::string& paste)
    {
        return paste;
    }

    std::string Error::canonical;
    bool Error::isError(const std::string& paste)
    {
        if (canonical.empty())
            return false;
        if (paste.size() <= canonical.size())
            return false;
        return !paste.compare(0, canonical.length(), canonical);
    }

    void CommandMap::insert(const std::string& name, const Command& cmd)
    {
        std::pair<iterator, bool> p(insert(std::make_pair(name, cmd)));
        if (!p.second)
        {
            if (p.first->second.second != cmd.second)
            {
                delete p.first->second.second;
            }
            p.first->second = cmd;
        }
    }

    CommandMap::~CommandMap(void)
    {
        for (iterator i(begin()), e(end()); i != e; ++i)
        {
            delete i->second.second;
        }
    }
}


namespace micker {

    struct BufferPack;

    struct Pstate
    {
        virtual void digest(char, BufferPack&) = 0;
        virtual ~Pstate() {}
    };

    struct BufferPack
    {
        CommandMap& cm;                ///< the command and value context for parsing
        std::string command;           ///< the micker command as parsed
        std::deque<std::string> args;  ///< the command's arguments as parsed

        /// @brief Set up an outmost parsing context
        ///
        BufferPack(CommandMap& cm, std::string::const_iterator c0, std::string::const_iterator e0)
            : cm(cm)
              , buf(c0, e0)
              , c(buf.begin())
              , e(buf.end())
            {}

        /// @brief Given an outer parsing context, start a subcontext
        ///
        BufferPack(BufferPack& bp, Pstate* ps)
            : cm(bp.cm)
              , buf(bp.c + 1, bp.e)
              , c(buf.begin())
              , e(buf.end())
            {
                // eat some prefix
                //
                transition(ps);

#               ifdef MICKER_VERBOSE_TRACING
                std::cout << "INSIDE Command is: '" << command << "' num of args: " << args.size() << "\n";
#               endif

                // update inherited buffer pack
                //
                bp.update(*this, run());
            }

        void transition(Pstate* ps)
            {
                // parse from buffer
                //
                for (; c != e; ++c)
                {
                    ps->digest(*c, *this);
                }

                // NOW: ps->finish();

                // dispose state, it is tainted anyway
                //
                delete ps;
            }

        std::string buf;
        std::string::const_iterator c;
        std::string::const_iterator e;

        bool exceptional(void) const
        {
            return c == e
                   && buf.begin() + 1 == c
                   && buf.end() != e
                   && *c == '[';
        }

        /// @brief When a command has been evaluated, inject the result
        ///        back into the outer parsing context
        ///
        void update(const BufferPack& sub, const std::string& insert)
            {
                // see if it evaluated to a problem
                if (Error::isError(insert))
                {
                    // destroy all pending buffer
                    buf = " ";
                    buf += insert;
                    c = buf.begin();
                    e = c + 1;
                    return;
                }

                // increment c
                //
                std::string::const_iterator c0(c++);

                // save the string portion pointed to by original c
                //
                std::string at(c0, c);

                // advance c by the amount consumed by sub
                //
                c += sub.e - sub.buf.begin();

                // construct a new, updated buffer
                //
                buf = at + insert + std::string(c, e);
                c = buf.begin();
                e = buf.end();
            }

        /// @brief After the command and its arguments have been determined, run
        ///        the command in the context of the command map.
        ///
        std::string run(void)
            {
                return cm.run(command, args);
            }

        /// When the return character is found then
        /// the end iterator will be the current iterator.
        /// To recommence parsing the end iterator must be
        /// readjusted.
        /// @return whether there is more to parse
        ///
        bool extend(void)
            {
                // purge command context
                //
                command.clear();
                args.clear();

                // set end
                //
                e = buf.end();
                return c != e;
            }
    };

    enum Where
    {
        beforeCommand,
        inCommand,
        inStringCommand,
        beforeArg,
        inArg,
        inStringArg
    };

    template <char RET>
    struct PStopping : Pstate
    {
        void beforeCommand_digest(char, BufferPack&);
        void inCommand_digest(char, BufferPack&);
        void inStringCommand_digest(char, BufferPack&);
        void beforeArg_digest(char, BufferPack&);
        void inArg_digest(char, BufferPack&);
        void inStringArg_digest(char, BufferPack&);
    };

    template <Where WHERE, char RET>
    struct PS : PStopping<RET>
    {
        /// default construct
        ///
        PS(void)
            {}

        /// construct and digest a char
        ///
        PS(char c, BufferPack& bp)
            {
                digest(c, bp);
            }

        /// digest a char into the buffer
        ///
        virtual void digest(char c, BufferPack& bp)
            {
                switch (WHERE)
                {
                    case beforeCommand: PStopping<RET>::beforeCommand_digest(c, bp); return;
                    case inCommand: PStopping<RET>::inCommand_digest(c, bp); return;
                    case inStringCommand: PStopping<RET>::inStringCommand_digest(c, bp); return;
                    case beforeArg: PStopping<RET>::beforeArg_digest(c, bp); return;
                    case inArg: PStopping<RET>::inArg_digest(c, bp); return;
                    case inStringArg: PStopping<RET>::inStringArg_digest(c, bp); return;
                }
            }
    };

    template <char RET>
    void PStopping<RET>::beforeArg_digest(char c, BufferPack& bp)
    {
        if (c == RET)
        {
            bp.e = bp.c + 1;
            return;
            /// fixme: finish() should give an error!
        }

        if (isspace(c))
        {
            return;
        }

        bp.args.push_back("");

        if (c == '"')
        {
            new (this) PS<inStringArg, '"'>;
        }
        else if (c == '{')
        {
            new (this) PS<inStringArg, '}'>;
        }
        else
        {
            new (this) PS<inArg, RET>(c, bp);
        }
    }

    template <char RET>
    void PStopping<RET>::inCommand_digest(char c, BufferPack& bp)
    {
        if (c == RET)
        {
            bp.e = bp.c + 1;
        }
        else if (c == '[')
        {
            // construct new buffer pack and parse/execute/splice
            BufferPack(bp, new PS<beforeCommand, ']'>);
        }
        else if (isalnum(c) || isgraph(c))
        {
            bp.command.push_back(c);
        }
        else if (isspace(c))
        {
            new (this) PS<beforeArg, RET>(c, bp);
        }
    }

    template <char RET>
    void PStopping<RET>::inStringCommand_digest(char c, BufferPack& bp)
    {
        if (c == RET)
        {
            new (this) PS<beforeArg, ']'>;
        }
        else if (RET == '"' && c == '[')
        {
            // construct new buffer pack and parse/execute/splice
            BufferPack(bp, new PS<beforeCommand, ']'>);
            new (this) PS<beforeCommand, ']'>;
        }
        else
        {
            bp.command.push_back(c);
        }
    }

    template <char RET>
    void PStopping<RET>::beforeCommand_digest(char c, BufferPack& bp)
    {
        if (c == RET)
        {
            bp.e = bp.c + 1;
        }
        else if (isspace(c))
        {
            return;
        }
        else if (c == '"')
        {
            new (this) PS<inStringCommand, '"'>;
        }
        else if (c == '{')
        {
            new (this) PS<inStringCommand, '}'>;
        }
        else
        {
            new (this) PS<inCommand, RET>(c, bp);
        }
    }

    template <char RET>
    void PStopping<RET>::inArg_digest(char c, BufferPack& bp)
    {
        if (c == RET)
        {
            bp.e = bp.c + 1;
        }
        else if (c == '[')
        {
            // construct new buffer pack and parse/execute/splice
            BufferPack(bp, new PS<beforeCommand, ']'>);
        }
        else if (isalnum(c) || isgraph(c))
        {
            bp.args.back().push_back(c);
        }
        else if (isspace(c))
        {
            new (this) PS<beforeArg, RET>(c, bp);
        }
    }

    template <char RET>
    void PStopping<RET>::inStringArg_digest(char c, BufferPack& bp)
    {
        if (c == RET)
        {
            new (this) PS<beforeArg, ']'>;
        }
        else if (RET == '"' && c == '[')
        {
            // construct new buffer pack and parse/execute/splice
            BufferPack(bp, new PS<beforeCommand, ']'>);
        }
        else
        {
            bp.args.back().push_back(c);
        }
    }

    std::string CommandMap::parseAndRun(const std::string& str)
    {
        BufferPack bp(*this, str.begin(), str.end());
        std::string result;

        do {
            bp.transition(new PS<beforeCommand, ';'>);

            if (!bp.command.empty())
            {
#               ifdef MICKER_VERBOSE_TRACING
                std::cout << "Command is: '" << bp.command << "' num of args: " << bp.args.size() << "\n";
#               endif
                result = run(bp.command, bp.args);

                if (Error::isError(result))
                {
                    return result;
                }
            }

            if (bp.exceptional())
            {
                bp.e = bp.buf.end();
                return std::string(bp.c, bp.e);
            }
            // there may be more input left
        } while (bp.extend());

        return result;
    }

} // namespace
